package parser;

import java_cup.runtime.*;
import java.util.*;
import java.math.BigInteger;
import java.io.*; 
import common.*; 

parser code {: 

    public parser(Reader input, OutputStream output) throws IOException {
        super(new Yylex(input));
        this.output = output;
    } 
    
    OutputStream output;
    static short VERSION = 12346;
    
    public void report_error(String message, Object info) {
        System.err.print(message + " at line " + ((Symbol)info).left);
    }
    
    public void syntax_error(Symbol cur_token) {
        report_error("Syntax error", cur_token);   
    }
    
    public void unrecovered_syntax_error(Symbol cur_token) throws Exception {
        report_fatal_error("Couldn't repair and continue parse", cur_token);
    }

:}

action code {:
    
    Map<Object, Integer> nameTable = new HashMap<Object, Integer>();
    Map<Object, Integer> constantTable = new HashMap<Object, Integer>();
    List<ClassHeader> classes = new ArrayList<ClassHeader>();
    CodeSequence expr = new CodeSequence();
    
    Scope scope;
    List functions = new ArrayList();
    
    Map flipMap(Map m) {
        Map result = new HashMap();
        for (Object o : m.entrySet()) {
            Map.Entry e = (Map.Entry)o;
            result.put(e.getValue(), e.getKey());   
        }
        return result;
    }
    
    void end_expr() {
        expr.writeTo(scope);
        expr = new CodeSequence();
    }
    
    void writeString(OutputStream out, String name) throws IOException {
        int len = name.length();
        out.write(len >> 8);
        out.write(len & 0xff);
        for (int i = 0; i < len; i++) {
            out.write(name.charAt(i));
        }
        if ((len & 1) != 0) {
            out.write(0);
        }
     }
        
    void writeNameTable(OutputStream out) throws IOException {
        Map names = flipMap(nameTable);
        int size = names.size();
        out.write(size >> 8);
        out.write(size & 0xff);
        for (int i = 0; i < size; i++) {
            writeString(out, (String)names.get(i));
        }
    }
    
    void writeConstantTable(OutputStream out) throws IOException {
        Map consts = flipMap(constantTable);
        int size = consts.size();
        out.write(size >> 8);
        out.write(size & 0xff);
        for (int i = 0; i < size; i++) {
            Object o = consts.get(i);
            if (o instanceof String) {
                writeString(out, (String)o);
            } else if (o instanceof BigInteger) {
                byte[] bytes = ((BigInteger)o).toByteArray();
                int len = bytes.length;
                out.write(len >> 8);
                out.write(len & 0xff);
                for (int j = 0; j < len; j++) {
                    out.write(bytes[j]);
                }
                if ((len & 1) != 0) {
                    out.write(0);
                }
            } else if (o instanceof Double) {
                out.write(0);
                out.write(8);
                long l = Double.doubleToRawLongBits((Double)o);
                out.write((int)(l >> 56));
                out.write((int)(l >> 48));
                out.write((int)(l >> 40));
                out.write((int)(l >> 32));
                out.write((int)(l >> 24));
                out.write((int)(l >> 16));
                out.write((int)(l >> 8));
                out.write((int)l);
            } else {
                throw new Error("Unexpected class " + o.getClass());
            }
        }
    }
    
    void writeTo(OutputStream out) throws IOException {
        out.write(parser.VERSION >> 8);
        out.write(parser.VERSION & 0xff);
        writeNameTable(out);
        writeConstantTable(out);
        out.write(functions.size() >> 8);
        out.write(functions.size() & 0xff);
        for (Object o: functions) {
            ((Scope)o).writeTo(out);
        }
        out.write(classes.size() >> 8);
        out.write(classes.size() & 0xff);
        for (ClassHeader c: classes) {
            c.writeTo(out);
        }
        scope.writeTo(out);
    }
    
    Integer constant(Object c) {
        Integer i = constantTable.get(c);
        if (i == null) {
            i = constantTable.size();
            constantTable.put(c, i);
        }
        return i;    
    }
    
    Integer name(Object s) {
        if (Names.isStandardName((String)s)) {
            return Names.getIndex((String)s);
        } else {
            Integer i = nameTable.get(s);
            if (i == null) {
                i = nameTable.size();
                nameTable.put(s, i);
            }
            return i + Names.standards();
        }
    }

    BigInteger MAX_BYTE = new BigInteger("127");
    BigInteger MIN_BYTE = new BigInteger("-128");
    BigInteger MAX_INT  = new BigInteger("32767");
    BigInteger MIN_INT  = new BigInteger("-32768");
    
    Object None = name("None"); 
        
    void member(Object nameID) {
        expr.emit2(Bytecodes.DESCRIPTOR, ((Integer)nameID));
    }
    
    void objectcall(Object param_count) {
        expr.emit2(Bytecodes.DESCRIPTOR, name("__call__"));
        expr.emit1(Bytecodes.CALL, ((Integer)param_count));
    }
    
    Integer argument(Object l) {
        expr.emit(Bytecodes.SWAP);
        return new Integer(((Integer)l) + 1);
    }
   
    void callexpr(Object param_count) {
        expr.emit1(Bytecodes.CALL, (Integer)param_count + 1);
    }
    
    void unary(String opname) {
        int id = name(opname);   
        expr.emit2(Bytecodes.DESCRIPTOR, id);
        expr.emit1(Bytecodes.CALL, 1);
    }

    void binop(String opname) {
        int id = name(opname);        
        expr.emit2(Bytecodes.DESCRIPTOR, id);
    }
    
    void binend() {
        expr.emit(Bytecodes.SWAP);
        expr.emit1(Bytecodes.CALL, 2);
    }
    
    void pType() {
        end_expr();
    }
    
    void noType() {
        scope.emit(Bytecodes.NONE);
    }

    static Integer ONE = new Integer(1); 
    static Integer TWO = new Integer(2); 
    
    Object classheader(Object name, Object supers) {
        end_expr();
        scope.emit2(Bytecodes.NEW_SCOPE, (Integer)name);
        int cls = classes.size();
        classes.add(new ClassHeader((Integer)name, (Integer)supers));
        return cls;
    }
    
    void endclass(Object cls) {
        int name = classes.get((Integer)cls).name;
        scope.emit(Bytecodes.POP_SCOPE);
        scope.emit1(Bytecodes.LOAD_CLASS, (Integer)cls);
        scope.emit2(Bytecodes.GLOBAL, name);
        scope.emit(Bytecodes.SET);
    }
    
    void newline(Object n) {
        scope.emit2(Bytecodes.LINE, (Integer)n); 
    }
    
    void var(Object n) {
        int name = (Integer)n;
        expr.emit2(scope.variable(), name);
    }
    
    CodeSequence type() {
        CodeSequence result = expr;
        expr = new CodeSequence();
        return result;
    }
    
    void binding(Object n) {
        end_expr();
        int name = (Integer)n;
        scope.bind(name); 
    }
    
    void _return() {
        end_expr();
        scope.emit(Bytecodes.RETURN);
    }
    
    void set(Object lhs) {
        end_expr();
        ((CodeSequence)lhs).writeTo(scope);
        scope.emit(Bytecodes.SET);
    }
     
    void get() {
        expr.emit(Bytecodes.GET);   
    }
     
    void raise() {
        end_expr();
        scope.emit(Bytecodes.RAISE);
    }
    
    Condition ifheader() {
        unary("__truth__");
        Condition cond = new Condition(expr);
        expr = new CodeSequence();
        cond.writeTo(scope);
        return cond;
    }
    
    Integer _else(Object cond) {
        int patch = scope.here();
        scope.emit2(0, 0);
        scope.emit(Bytecodes.ELSE);
        ((Condition)cond).target(scope);
        return patch;
    }
    
    void _noelse(Object cond) {
        ((Condition)cond).target(scope);
    }

    void end_if(Object patch) {
        scope.patch2((Integer)patch, Bytecodes.DONE_IF, scope.here() - (Integer)patch);
    }
    
    void ifstmt(Object cond) {
        ((Condition)cond).target(scope);
    }
    
    void _import(Object name) {
        // scope.emit2(Bytecodes.IMPORT, ((Integer)name));   
    }
    
    void exprstmt() {
        end_expr();
        scope.emit(Bytecodes.DROP);   
    }
    
    While whileheader() {
        scope.emit(Bytecodes.WHILE);
        int patch1 = scope.here();
        end_expr();
        unary("__truth__");
        int patch2 = scope.here();
        scope.emit2(0, 0);
        While result = new While(patch1, patch2);
        return result; 
    }
    
    void whilestmt(Object w) {
        While wh = (While)w;
        scope.patch2(wh.patch2, Bytecodes.DO, scope.here() - wh.patch2);
        scope.emit2(Bytecodes.END_LOOP, wh.patch1 - scope.here());
    }
    
    void functionheader(Object name, Object parameters) {
        List plist = (List)parameters;
        scope = scope.beginFunction((Integer)name, plist);
        functions.add(scope);
    }
    
    void endfunction() {
        scope.emit(Bytecodes.NONE); // Return NONE at end...
        scope.emit(Bytecodes.RETURN);
        scope = scope.close();
    }
    
    void endnative() {
        scope = scope.close();
    }
    
    Object parameterlist(Object p) {
        List l = new ArrayList();
        l.add(p);
        return l;
    }
    
    Object extendparameterlist(Object l, Object p) {
        ((List)l).add(p);
        return l;
    }
    
    Object extendlist(Object l) {
        return new Integer(((Integer)l) + 1);
    }
    
    Object parameter(Object name, Object type) {
        return new Parameter(name, type);
    }
    
    void done() {
        try {   
            writeTo(parser.output);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
    
    void string(Object s) {
        expr.emit2(Bytecodes.STRING, constant(s));
    }
    
    void integer(Object i) {
        BigInteger b = (BigInteger)i;   
        if(b.compareTo(MAX_BYTE) <= 0 && b.compareTo(MIN_BYTE) >= 0) {
            expr.emit1(Bytecodes.BYTE, b.intValue());
        } else if(b.compareTo(MAX_INT) <= 0 && b.compareTo(MIN_INT) >= 0) {
            expr.emit2(Bytecodes.INTEGER, b.intValue());
        } else {
            expr.emit2(Bytecodes.LONG, constant(i));
        }
    }
    
    void real(Object f) {
        expr.emit2(Bytecodes.FLOAT, constant(f));
    }
    
    void _bool(boolean b) {
        expr.emit(b ? Bytecodes.TRUE : Bytecodes.FALSE);
    }
   
    void none() {
        expr.emit(Bytecodes.NONE);
    }
/*    
    void _else(Object cond) {
        List l = (List)patchlist;
        int last = l.size() - 1;
        if (last >= 0) {
            scope.size -= 3;
        }
        scope.emit(Bytecodes.END_IF);
        for (int i = 0; i < last; i++) { 
            Object patch = l.get(i);
            scope.patch2((Integer)patch, Bytecodes.DONE_IF, scope.here() - (Integer)patch);
        }
    }
    
    void if_else(Object patchlist) {
        scope.emit(Bytecodes.END_IF);
        for(Object patch : (List)patchlist) {
            scope.patch2((Integer)patch, Bytecodes.DONE_IF, scope.here() - (Integer)patch);
        }
    }
    
    Object base_if() {
        List l = new ArrayList();
        l.add(scope.here());
        scope.emit2(0, 0);
        return l;
    }
    
    Object _elif(Object l) {
        List list = (List)l;
        list.add(scope.here());
        scope.emit2(0, 0);
        return list;
    }
*/    
    Integer except(Object patch) {
        int patch2 = scope.here();
        scope.emit2(0,0); 
        scope.patch2((Integer)patch, Bytecodes.PROTECT, scope.here() - (Integer)patch);
        end_expr();
        scope.emit1(Bytecodes.CATCH, 1);
        return patch2;
    }
    
    Object  trystart() {
        int patch = scope.here();
        scope.emit2(0, 0);
        return patch;
    }
    
    void tryend(Object patch) {
        scope.patch2((Integer)patch, Bytecodes.END_PROTECT, scope.here() - (Integer)patch);
    }
    
    Object tryend(Object handler, Object type) {
        Object result = scope.here();
        scope.emit(Bytecodes.DROP);
        ((Handler)handler).address = scope.here();
        scope.emit2(0, 0);
        ((Handler)handler).type = (Integer)type;
        return result;
    }

    void forstmt() {
        throw new Error("Not implemented");
    }

:};

init with {: 
   action_obj.scope = new ModuleScope();
:};


/* Terminals (tokens returned by the scanner). */

terminal AS;
terminal AND, OR;
terminal NOT;
terminal LT, GT, LE, GE, EQ, NE;
terminal IDENTIFIER;
terminal TILDE;
terminal PLUS;
terminal MINUS;
terminal MULT;
terminal REM;
terminal DIV;
terminal STRING;
terminal INTEGER;
terminal FLOAT;
terminal IMAGINARY;
terminal L_PAREN;
terminal R_PAREN;
terminal L_BRACKET;
terminal R_BRACKET;
terminal COLON;
terminal DOT;
terminal POWER;
terminal COMMA;
terminal ASSIGN;
terminal IN;
terminal IF;
terminal ELSE;
terminal ELIF;
terminal THEN;
terminal WHILE;
terminal RETURN;
terminal FOR;
terminal DEDENT;
terminal INDENT;
terminal NEWLINE;
terminal DEF;
terminal CLASS;
terminal IMPORT;
terminal PRINT;
terminal ILLEGAL_INDENT;
terminal ILLEGAL_CHARACTER;
terminal UNARY;
terminal TRUE;
terminal FALSE;
terminal PASS;
terminal NONE;
terminal TRY;
terminal EXCEPT;
terminal RAISE;
terminal NATIVE;

/* Non terminals */
non terminal newline;
non terminal module;
non terminal statement;
non terminal function;
non terminal classdef;
non terminal expression;
non terminal member;
non terminal expression_list;
non terminal arguments;
non terminal bound;
non terminal slice;
non terminal onelinestmt;
non terminal classheader;
non terminal suite;
non terminal superclass;
non terminal classlist;
non terminal statementlist;
non terminal parameterlist;
non terminal parameter;
non terminal name;
non terminal object;
non terminal ifcondition;
non terminal elifcondition;
non terminal whilecondition;
non terminal nativefunction;
non terminal functionheader;
non terminal file;
non terminal ifstatement;
non terminal ifnoelse;
non terminal ifsuite;
non terminal elifsuite;
non terminal elifnoelse;
non terminal elifstatement;
non terminal elsepart;
non terminal member_assign;
non terminal try;
non terminal type;
non terminal trybody;
non terminal trystatement;
non terminal lvalue;
non terminal exceptname;
non terminal lvalue_list;
non terminal multiple_assignment;

precedence left AND, OR;
precedence left NOT;
precedence left LT, GT, LE, GE, EQ, NE;
precedence left PLUS, MINUS;
precedence left MULT, DIV, REM;
precedence left UNARY;
precedence right POWER;

start with file;

file ::= module {: done(); :} ;

module ::= module statement |
           module classdef  |
           module newline   |
           statement        |
           classdef         |
           newline          ;
                                      
object ::=  STRING:s                                    {: string(s);  :} | 
            INTEGER:j                                   {: integer(j);  :} |
            FLOAT:f                                     {: real(f);  :}  |
            L_PAREN expression:e R_PAREN                {:   :} | 
            TRUE                                        {:  _bool(true);  :}  |
            FALSE                                       {:  _bool(false); :}   |
            NONE                                        {:  none(); :}   |
            name:n {: var(n); :} arguments:p            {:  callexpr(p);   :}   |
            member  arguments:p                         {:  callexpr((Integer)p);    :} |
            object arguments:p                          {:  objectcall(p);  :}     |
            object:o  L_BRACKET expression:i R_BRACKET  {: /* indexexpr(o, i); */  :} |
            object:o  L_BRACKET slice:s R_BRACKET       {: /* sliceexpr(o, s); */  :} ;
         
expression ::=  member                      {:  get();  :}          |
                object                                                  |
                name:n                      {:  var(n); get(); :}       |
                expression PLUS   {: binop("__add__"); :} expression {: binend(); :} |
                expression MINUS  {: binop("__sub__"); :} expression {: binend(); :} |
                expression MULT   {: binop("__mul__"); :} expression {: binend(); :} |
                expression REM    {: binop("__rem__"); :} expression {: binend(); :} |
                expression DIV    {: binop("__div__"); :} expression {: binend(); :} |
                expression AND    {: binop("__and__"); :} expression {: binend(); :} |
                expression OR     {: binop("__or__");  :} expression {: binend(); :} |
                expression POWER  {: binop("__pow__"); :} expression {: binend(); :} |
                expression LT     {: binop("__lt__");  :} expression {: binend(); :} |
                expression GT     {: binop("__gt__");  :} expression {: binend(); :} |
                expression LE     {: binop("__le__");  :} expression {: binend(); :} |
                expression GE     {: binop("__ge__");  :} expression {: binend(); :} |
                expression EQ     {: binop("__eq__");  :} expression {: binend(); :} |
                expression NE     {: binop("__ne__");  :} expression {: binend(); :} |
                NOT expression                {:  unary("__not__");   :}  |
                PLUS   expression             {:  unary("__uadd__");  :}  %prec UNARY |
                MINUS  expression             {:  unary("__neg__");   :}  %prec UNARY |
                TILDE  expression             {:  unary("__inv__");   :}  %prec UNARY ;

name ::= IDENTIFIER:i {:  RESULT = name(i);  :} ;
                        
member ::= object DOT name:n  {:  member(n);  :}  |
           member DOT name:n  {:  get(); member(n);  :}  |
           name:o DOT name:n  {:  var(o); get(); member(n);  :} ;
           
lvalue ::= name | member ;

lvalue_list ::= lvalue_list COMMA lvalue | lvalue ;
		   
arguments ::= L_PAREN R_PAREN                   {:  RESULT = 0;  :}  |
              L_PAREN expression_list:l R_PAREN {:  RESULT = l;  :}  ;
                                                   
expression_list ::= expression:e                         {:  RESULT = argument(0);          :} |
                    expression_list:l COMMA expression:e {:  RESULT = argument((Integer)l); :} ;
      
bound ::=             {: /* RESULT = None; */  :} |
           expression {: /* RESULT = e; */  :}  ;
                                     
slice ::= bound:b1 COLON bound:b2  {: /* RESULT = slice(b1, b2); */  :}  ;
 
multiple_assignment ::= L_PAREN lvalue_list R_PAREN ASSIGN expression_list     |
                        L_BRACKET lvalue_list R_BRACKET ASSIGN expression_list ;

whilecondition ::= WHILE expression   {: RESULT = whileheader(); :}  ;

member_assign ::= member ASSIGN {: RESULT = expr; expr = new CodeSequence(); :}  ;

type ::=                  {: RESULT = null;   :} |
         COLON expression {: RESULT = type(); :};

onelinestmt ::= name:n ASSIGN  expression                       {: binding(n); :} |
                member_assign:x  expression:e                   {: set(x);  :} |
                ifcondition:i THEN onelinestmt                  {: ifstmt(i);        :}  |
                IMPORT name:n                                   {: _import(n);  :}     |
                expression:e                                    {: exprstmt();   :}     |
                PRINT {: var(name("print")); :} expression      {: callexpr(argument(0)); exprstmt();  :} |
                RETURN expression                               {: _return(); :} |
                RETURN                                          {: none(); _return(); :} |
                PASS                                            {:  :} |
                RAISE expression                                {: raise(); :} ;
                                                         
statement ::=  ifstatement                               {:  :}              |
               whilecondition:w COLON suite              {: whilestmt(w); :} |
               FOR name:m IN expression:e COLON suite    {: forstmt();    :} |
               function                                  {:  :}              |
               onelinestmt:s newline                     {:  :}              |
               trystatement                              {:  :}              ;
               
try ::= TRY  {: RESULT = trystart(); :} ;

trystatement ::= trybody:t COLON suite {: tryend(t); :} ;
          
trybody ::= try:t COLON suite EXCEPT expression {: RESULT = except(t); :} exceptname  ;

exceptname ::= /* empty */ {: exprstmt(); :} |
               AS name:n   {: binding(n); :} ;

ifnoelse ::= ifcondition:i COLON suite {: _noelse(i); :} ;

elifnoelse ::= elifcondition:i COLON suite {: RESULT = _else(i); :} ;

ifsuite ::= ifcondition:i COLON suite {: RESULT = _else(i); :} ;

elifsuite ::= elifcondition:i COLON suite {: RESULT = _else(i); :} ;

ifstatement ::= ifnoelse  |
                ifsuite:i elsepart {: end_if(i); :} ;

elifstatement ::= elifnoelse:i {: _noelse(i);  :} |
                  elifsuite:i elsepart {: end_if(i); :} ;

elsepart ::= ELSE COLON suite  |
             elifstatement  ;
                
ifcondition ::= IF expression {: RESULT = ifheader(); :}  ;
                
elifcondition ::= ELIF expression:e {: RESULT = ifheader(); :} ;

/*
ifstatement ::= ifnoelse:l   {: no_else(l); :}  | 
                ifnoelse:l ELSE COLON suite   {: if_else(l); :}   ;
               
ifnoelse ::= ifcondition:i COLON suite {: ifstmt(i); RESULT = base_if(); :}  |
             ifnoelse:i elifstmt       {: RESULT = _elif(i); :} ;
                
elifstmt ::= elifcondition:e COLON suite {: ifstmt(e); :} ;
*/
suite ::= onelinestmt newline  {: :}  |
          newline INDENT statementlist:s DEDENT {:  :} ;

statementlist ::= statement:s                 {:  :} | 
                  statementlist:l statement:s {:  :} ;
                  
functionheader ::= DEF name:n L_PAREN parameterlist:a R_PAREN   {: functionheader(n, a); :}  |
                   DEF name:n L_PAREN R_PAREN                   {: functionheader(n, Collections.EMPTY_LIST); :}  ;                             
                                                                              
nativefunction ::= NATIVE name:n L_PAREN parameterlist:a R_PAREN newline   {: functionheader(n, a); endnative(); :}  |
                   NATIVE name:n L_PAREN R_PAREN newline                   {: functionheader(n, Collections.EMPTY_LIST); endnative(); :}  ;                             
                                                                              
function ::= functionheader COLON suite  {: endfunction();  :}  |
             nativefunction ; 

parameterlist ::= parameter:a                      {: RESULT = parameterlist(a);  :} |
                 parameterlist:l COMMA parameter:a {: RESULT = extendparameterlist(l, a); :}  ; 
                
parameter ::= name:n                  {:  noType(); RESULT = n; :} |
              name:n COLON expression {:  pType(); RESULT = n;  :}   ;
           
classheader ::= CLASS name:n superclass:s COLON  {: RESULT = classheader(n, s); :} ;

classdef ::= classheader:h suite {: endclass(h); :} ;

superclass ::=   {:  RESULT = 1; var(name("object")); get();  :}      |
               L_PAREN classlist:c R_PAREN  {:  RESULT = c;   :}   ;
               
classlist ::=  expression                    {:  RESULT = 1;     :}  |
               classlist:l COMMA expression  {:  RESULT = extendlist(l);   :}  ;
              
newline ::= NEWLINE:n {: newline(n); :} ;

